為了這個簡單有趣的效果,從 Day7 一路寫到現在 Day17 也是滿妙的,太小看這個 API 了 XD
官方 Demo:https://vueuse.org/core/useParallax/#useparallax
這邊先不加入視差特效,先從切版來了解 HTML 結構以及樣式部分是怎麼設計的,說明用註解的方式寫在以下程式碼內:
<script setup>
import { computed, reactive, ref } from 'vue'
import { useParallax } from '@/compositions/useParallax'
const target = ref(null)
const parallax = reactive(useParallax(target))
// 整個視差效果的容器樣式,有多做一個 border 標示,最外層的 border 就是這個 target 的範圍
const targetStyle = {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
border: '1px solid #cdcdcd',
transition: '.3s ease-out all',
}
// 主要用途 -> perspective: '300px' 建立 3D 空間,影響子元素的 3D 變換效果。
const containerStyle = {
margin: '3em auto',
perspective: '300px',
}
// 卡片基本樣式
const cardStyle = computed(() => ({
background: '#fff',
height: '20rem',
width: '15rem',
borderRadius: '5px',
border: '1px solid #cdcdcd',
overflow: 'hidden',
transition: '.3s ease-out all',
boxShadow: '0 0 20px 0 rgba(255, 255, 255, 0.25)',
}))
// 卡片內部的小視窗,子元素圖片都會在視窗裡呈現
const cardWindowStyle = {
overflow: 'hidden',
fontSize: '6rem',
position: 'absolute',
top: 'calc(50% - 1em)',
left: 'calc(50% - 1em)',
height: '2em',
width: '2em',
margin: 'auto',
}
// 定義所有圖層的基本樣式,讓他們填滿卡片內部的小視窗
const layerBase = {
position: 'absolute',
height: '100%',
width: '100%',
transition: '.3s ease-out all',
}
const layer0 = computed(() => ({
...layerBase,
}))
const layer1 = computed(() => ({
...layerBase,
}))
const layer2 = computed(() => ({
...layerBase,
}))
const layer3 = computed(() => ({
...layerBase,
}))
const layer4 = layerBase
</script>
<template>
<div>
<div ref="target" :style="targetStyle">
<div :style="containerStyle">
<div :style="cardStyle">
<div :style="cardWindowStyle">
<img
:style="layer0"
src="https://jaromvogel.com/images/design/jumping_rabbit/page2layer0.png"
alt=""
>
<img
:style="layer1"
src="https://jaromvogel.com/images/design/jumping_rabbit/page2layer1.png"
alt=""
>
<img
:style="layer2"
src="https://jaromvogel.com/images/design/jumping_rabbit/page2layer2.png"
alt=""
>
<img
:style="layer3"
src="https://jaromvogel.com/images/design/jumping_rabbit/page2layer3.png"
alt=""
>
<img
:style="layer4"
src="https://jaromvogel.com/images/design/jumping_rabbit/page2layer4.png"
alt=""
>
</div>
</div>
</div>
<div class="note opacity-1">
Credit of images to
<a
href="https://codepen.io/jaromvogel"
target="__blank"
>Jarom Vogel</a>
</div>
</div>
</div>
</template>
每一個圖層的圖片,可以在官方 Demo 中用 chrome 開發者工具看到,這邊怕版面太亂,就不貼上來。
這邊 layer4 比較有趣一點,看起來是用來做不規則圓角的遮罩,或是可以想像成相框(可以把 cardStyle 的背景色改成黑色觀察看看)。另外他在 HTML 中被排在最後一個是有用意的,如果是在第一個,就會被後面的元素蓋過,導致沒有相框效果(或是要額外 z-index 處理)。
// ...略
const cardStyle = computed(() => ({
// ...略
transform: `rotateX(${parallax.roll * 20}deg) rotateY(${
parallax.tilt * 20
}deg)`,
}))
這邊要釐清 rotateX 跟 parallax.roll、rotateY 跟 parallax.tilt 的關係,以 rotateX 為例,
rotateX 是讓元素繞著 X 軸轉,roll 也是一樣的特性(roll 的相關資訊可參考 Day 16)
所以當滑鼠從元素原點往上移動時,roll 值也會越來越大(最大 0.5),rotateX 的角度也會越來越大,這樣就可以達成滑鼠往上移,元素跟著傾斜的效果。
// ...略
const layer0 = computed(() => ({
...layerBase,
transform: `translateX(${parallax.tilt * 10}px) translateY(${
parallax.roll * 10
}px) scale(1.33)`,
}))
const layer1 = computed(() => ({
...layerBase,
transform: `translateX(${parallax.tilt * 20}px) translateY(${
parallax.roll * 20
}px) scale(1.33)`,
}))
const layer2 = computed(() => ({
...layerBase,
transform: `translateX(${parallax.tilt * 30}px) translateY(${
parallax.roll * 30
}px) scale(1.33)`,
}))
const layer3 = computed(() => ({
...layerBase,
transform: `translateX(${parallax.tilt * 40}px) translateY(${
parallax.roll * 40
}px) scale(1.33)`,
}))
// ...略
這邊做的事其實跟前面的 cardStyle 類似,差別在用 translateX、translateY 做效果。另外看到有 x10、x20、x30、x40 不同的倍率,可以想成我們坐在火車上看窗外的風景,最遠的山看起來移動緩慢,最近的草(?)看起來移動最快,所以 x40 就是離我們最近的圖層,要讓他移動幅度大一點,效果比較逼真。
GitHub:https://github.com/RhinoLee/30days_vue/pull/16/files
為了完成這個視差效果,中間一路看了 useEventListener、useMouse、useMouseInElement、useMounted、useSupported、最後的 useParallx,這種感覺其實還不錯,有一種已經完賽的感覺(並沒有)。
明天開始就來看另一個滿常用的 useInfiniteScroll API,節奏應該會跟 useParallx 一樣,中間有用到其他的 vueuse API 就會先進去看,最後再回到 useInfiniteScroll 本身。